package org.simplesocket.simplenettycore.handlers;

import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
import org.simplesocket.simplenettycore.context.InjectContext;
import org.simplesocket.simplenettycore.context.SimpleNettyContext;
import org.simplesocket.simplenettycore.exceptions.GlobalExceptionHandler;
import org.simplesocket.simplenettycore.protocol.Request;
import org.simplesocket.simplenettycore.protocol.SimpleProtocol;
import org.simplesocket.simplenettycore.route.InjectCTX;
import org.simplesocket.simplenettycore.route.InjectParam;
import org.simplesocket.simplenettycore.route.InjectRequest;
import org.simplesocket.simplenettycore.route.Router;
import org.simplesocket.simplenettycore.utils.CommonUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Slf4j
@ChannelHandler.Sharable
public class DistributeRouteHandler extends SimpleChannelInboundHandler<Request> {
    
    private final SimpleNettyContext context;
    
    public DistributeRouteHandler(SimpleNettyContext context) {
        this.context = context;
    }
    
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Request request) {
        context.getSimpleNettyConfig().getWorker().submit(() -> {
            Router router;
            Map<String, ?> params;
           try {
               String uri = request.getUri();
               params = request.getParams();
               router = context.getRouter().get(uri);
               if (router == null){
                   writeErrorBack(ctx,request,"No such uri");
                   return;
               }

           }catch (Exception e){
               writeErrorBack(ctx,request,"Can not find router");
               return;
           }

            injectAndInvoke(ctx,router, params, request);// 通过反射为方法注入实参并执行路由方法

        });
    }

    private void injectAndInvoke(ChannelHandlerContext ctx,Router router,
                                   Map<String,?> params,Request request){
        Method method = router.getMethod();
        List<Object> paramsList = new ArrayList<>();
        for (Parameter parameter : method.getParameters()) {
            Annotation[] annotations = parameter.getAnnotations();
            if (annotations == null || annotations.length == 0) {
                writeErrorBack(ctx,request);
                throw new RuntimeException("If you want to use the parameter '" + parameter.getName()
                        + "' on the routing method, please add an annotation");
            }

            for (Annotation annotation : annotations) {
                switch (annotation) {
                    case InjectCTX ignored -> paramsList.add(ctx);
                    case InjectRequest ignored -> paramsList.add(request);
                    case InjectContext ignored -> paramsList.add(context);
                    case InjectParam injectParam -> {
                        String paramName = injectParam.value();
                        if (StrUtil.isBlank(paramName)) {
                            writeErrorBack(ctx,request);
                            throw new RuntimeException("Annotation '"
                                    + annotation.annotationType().getTypeName()
                                    + "' value is empty");
                        }
                        boolean required = injectParam.required();
                        Object param = params.get(paramName);
                        if (param == null) {
                            if (!required) {
                                paramsList.add(null);
                            } else {
                                writeErrorBack(ctx,request);
                                throw new RuntimeException("Required parameter '" + paramName + "' not found");
                            }
                        } else {
                            Class<?> parameterType = parameter.getType();
                            try {
                                if (parameterType.isPrimitive() ||
                                        ClassUtil.isPrimitiveWrapper(parameterType)) {
                                    // 如果是普通数据类型或者普通类型的包装类
                                    paramsList.add(param);
                                } else if (parameterType.equals(String.class)) {// String 类型
                                    paramsList.add(param);
                                } else { // 其他对象类型
                                    JSONObject paramJSONObject = (JSONObject) param;
                                    paramsList.add(paramJSONObject.toJavaObject(parameterType));
                                }
                            }catch (Exception e){
                                log.error("Parameter value transform error\n");
                                CommonUtils.printException(e);
                                writeErrorBack(ctx,request);
                            }
                        }
                    }
                    default -> {
                        writeErrorBack(ctx,request);
                        throw new RuntimeException("No such annotation '"
                                + annotation.annotationType().getTypeName() + "'");
                    }
                }
            }
        }

        method.setAccessible(true);
        try {
            Object invoked = method.invoke(router.getInstance(), paramsList.toArray());
            if (invoked != null) {
                ctx.writeAndFlush(SimpleProtocol.builder()
                                .type(SimpleProtocol.Type.NORMAL)
                                .status(SimpleProtocol.Status.SUCCESS)
                                .sessionID(request.getSessionID())
                                .uri(request.getUri())
                                .data(JSON.toJSONString(invoked))
                        .build());
            }
        } catch (Exception e) {
            log.error("Invoke a routing method error:\n");
            CommonUtils.printException(e);
            Object o = context.getInstances().get(GlobalExceptionHandler.class.getName());
            assert o != null;
            if (o instanceof GlobalExceptionHandler<?> handler){
                Object handled = handler.handle(e);
                writeErrorBack(ctx,request,JSON.toJSONString(handled));
            }else {
                writeErrorBack(ctx,request);
            }
        }finally {
            request.recycle();
        }
    }

    private void writeErrorBack(ChannelHandlerContext ctx,Request request){
        ctx.writeAndFlush(SimpleProtocol.builder()
                .type(SimpleProtocol.Type.NORMAL)
                .status(SimpleProtocol.Status.ERROR)
                .sessionID(request.getSessionID())
                .uri(request.getUri())
                .data(JSON.toJSONString(Map.of("message","Request error")))
                .build());
    }

    private void writeErrorBack(ChannelHandlerContext ctx,Request request,String msg){
        ctx.writeAndFlush(SimpleProtocol.builder()
                .type(SimpleProtocol.Type.NORMAL)
                .status(SimpleProtocol.Status.ERROR)
                .sessionID(request.getSessionID())
                .uri(request.getUri())
                .data(JSON.toJSONString(Map.of("message",msg)))
                .build());
    }
}
